package com.gitee.wsl.compose.ui.swiperefresh.sample.state

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.spring
import androidx.compose.foundation.MutatorMutex
import androidx.compose.runtime.*
import com.gitee.wsl.compose.ui.swiperefresh.state.ActionStatus
import kotlinx.coroutines.delay
import timber.log.Timber
import kotlin.math.absoluteValue


/**
 * Helper function to create a [RefreshLayoutState] that is remembered across compositions.
 */
@Composable
fun rememberRefreshLayoutState(): RefreshLayoutState {
    return remember { RefreshLayoutState() }
}

/**
 * Internal state of the [RefreshLayout], used to track the current state of the refresh and load and control
 * the scroll offset.
 */
class RefreshLayoutState {

    internal val maxScrollUpPx: Float
        get() = loadingMoreState.triggerDistancePx.absoluteValue + 100

    internal val maxScrollDownPx: Float
        get() = refreshingState.triggerDistancePx + 100

    /**
     * The current offset of the refresh indicator.
     */
    private val _offsetY = Animatable(0f)

    val refreshingState = Action.Refreshing(_offsetY.asState())
    val loadingMoreState = Action.LoadingMore(_offsetY.asState())

    val isRefreshing:Boolean get() = refreshingState.componentStatus.isLoading

    val isFinishing:Boolean get() = refreshingState.componentStatus.isFinishing

    val isLoading:Boolean get() = loadingMoreState.componentStatus.isLoading


    private val mutatorMutex = MutatorMutex()
    val indicatorOffset: Float
        get() = _offsetY.value

    internal suspend fun animateOffsetTo(v: Float) {
        return mutatorMutex.mutate {
            if (indicatorOffset != v) {
                _offsetY.animateTo(v)
            }
        }
    }

    internal suspend fun dispatchScrollDelta(delta: Float) {
        if (delta.absoluteValue < 0.001f) {
            return
        }
        mutatorMutex.mutate {
            var newOffsetY = _offsetY.value + delta
            Timber.d("dispatchScrollDelta: delta=$delta,newOffsetY=$newOffsetY")
            if (newOffsetY > 0) {
                //scroll down
                if (newOffsetY >= refreshingState.triggerDistancePx && refreshingState.hasMoreData) {
                    refreshingState.updateComponentStatus(ActionStatus.ReadyForAction)
                } else {
                    refreshingState.updateComponentStatus(ActionStatus.Dragging)
                }
                newOffsetY = newOffsetY.coerceAtMost(refreshingState.triggerDistancePx + 100f)
            } else if (newOffsetY < 0) {
                //scroll up
                if (newOffsetY.absoluteValue >= loadingMoreState.triggerDistancePx && loadingMoreState.hasMoreData) {
                    loadingMoreState.updateComponentStatus(ActionStatus.ReadyForAction)
                } else {
                    loadingMoreState.updateComponentStatus(ActionStatus.Dragging)
                }
                newOffsetY = newOffsetY.coerceAtLeast(-loadingMoreState.triggerDistancePx - 100f)
            }
            _offsetY.snapTo(newOffsetY)
        }
    }

    internal fun startRefresh() {
        refreshingState.updateComponentStatus(ActionStatus.InProgress)
    }


    internal fun startLoadMore() {
        loadingMoreState.updateComponentStatus(ActionStatus.InProgress)
    }

    internal fun idle() {
        if (refreshingState.componentStatus != ActionStatus.IDLE) {
            refreshingState.updateComponentStatus(ActionStatus.IDLE)
        }
        if (loadingMoreState.componentStatus != ActionStatus.IDLE) {
            loadingMoreState.updateComponentStatus(ActionStatus.IDLE)
        }
    }

    /**
     * Finish load more
     *
     * @param success load more success or not
     * @param hasMoreData has more data or not
     * @param delay Duration of the result message display
     */
    suspend fun finishLoadMore(success: Boolean, hasMoreData: Boolean, delay: Long = 1000) {
        if (loadingMoreState.componentStatus == ActionStatus.InProgress) {
            loadingMoreState.updateComponentStatus(
                if (success) ActionStatus.Success else ActionStatus.Failed
            )
            delay(delay)
            loadingMoreState.updateComponentStatus(ActionStatus.Resetting)
        }
        loadingMoreState.hasMoreData = hasMoreData
    }

    /**
     * Finish refresh
     *
     * @param success refresh success or not
     * @param hasMoreData has more data or not
     * @param delay Duration of the result message display
     */
    suspend fun finishRefresh(
        success: Boolean,
        hasMoreData: Boolean = true,
        delay: Long = 1000
    ) {
        if (refreshingState.componentStatus == ActionStatus.InProgress) {
            refreshingState.updateComponentStatus(
                if (success) ActionStatus.Success else ActionStatus.Failed
            )
            delay(delay)
            refreshingState.updateComponentStatus(ActionStatus.Resetting)
        }
//        refreshingState.hasMoreData = hasMoreData
        loadingMoreState.hasMoreData = hasMoreData
    }


    suspend fun refresh(animationSpec: AnimationSpec<Float> = spring()) {
        _offsetY.animateTo(refreshingState.triggerDistancePx, animationSpec = animationSpec)
        startRefresh()
    }

    suspend fun loadMore(animationSpec: AnimationSpec<Float> = spring()) {
        _offsetY.animateTo(-loadingMoreState.triggerDistancePx, animationSpec = animationSpec)
        startLoadMore()
    }

}
